/**
 * 
 */
package edu.arizona.bio5.onto.modularity;

import java.net.URI;
import java.util.HashSet;
import java.util.Set;

import org.semanticweb.owlapi.model.OWLAxiom;

import edu.arizona.bio5.onto.decomposition.Atom;
import edu.arizona.bio5.onto.decomposition.AtomicDecomposition;
import edu.arizona.bio5.onto.decomposition.MSSLabel;

/**
 * Extracts a module by examining seed signatures stored in atom's labels
 * 
 * @author Pavel Klinov
 *
 */
public class SeedSigBasedModuleExtractor implements ModuleExtractor {

	public static int IRRELEVANT_COUNT = 0;
	public static int RELEVANT_COUNT = 0;
	
	private AtomicDecomposition m_ad = null;
	private int m_relAtomNumber = 0;
	
	public int getRelevantAtomNumber() {
		
		return m_relAtomNumber;
	}
	
	private MSSLabel getMSSLabel(Atom atom) {
		return (MSSLabel) atom.getLabel(MSSLabel.TYPE); 
	}	
	
	/*
	 */
	@Override
	public Set<OWLAxiom> extract(Signature sig, boolean annotations) {

		Set<Atom> relAtoms = findRelevantAtoms(sig, m_ad);
		
		m_relAtomNumber = relAtoms.size();
		
		return extractFromRelevantAtoms(relAtoms, sig, m_ad, annotations);
	}

	/*
	 * Finds all atoms whose principal ideals *may* be relevant for the seed signature.
	 */
	private Set<Atom> findRelevantAtoms(Signature seedSignature, AtomicDecomposition ad) {
		
		//GlobalTimer.start("find_relevant");
		
		Set<Atom> irrelAtoms = new HashSet<Atom>();
		Set<Atom> relAtoms = new HashSet<Atom>();
		Set<Atom> possRelAtoms = new HashSet<Atom>();
		Signature newSig = new Signature(seedSignature);
		Set<Atom> newAtoms = new HashSet<Atom>();
		
		do {
		
			for (URI term : newSig) {
				/*
				 * Here's the crucial bit for correctness in the presence of dirty atoms
				 * In that case we have to consider even those atoms in whose computed labels
				 * the term does not occur. Because it can occur in a seed signature that we've failed to compute.
				 * So in that unfortunate case we have to look at every atom whose signature contains the term and suffer-suffer-suffer...
				 */
				Set<Atom> termAtoms = /*m_termIndex.get(term);*/ad.getTermBasedIndex().get(term);
				
				if (termAtoms == null) continue;
			
				for (Atom termAtom : termAtoms) {
					//if (termAtom.getLabel() == null) throw new RuntimeException("Atom not labeled: " + atom);
					//Check if we've seen this atom before
					if (irrelAtoms.contains(termAtom) || relAtoms.contains(termAtom)) continue;
					
					filter(termAtom, ad, seedSignature, relAtoms, irrelAtoms, possRelAtoms, newAtoms);
				}
			}
			//See if the signature got expanded
			newSig.clear();
			
			for (Atom newAtom : newAtoms) {
				//GlobalTimer.start("get_sig");	
				newSig.addOWLEntities(newAtom.getSignature());
				//GlobalTimer.stop("get_sig");
			}

			newSig.remove(seedSignature);
			seedSignature.add(newSig);			
			irrelAtoms.clear();
			newAtoms.clear();
			
		} while (!newSig.isEmpty());
		
		relAtoms.addAll(possRelAtoms);
		
		//GlobalTimer.stop("find_relevant");
		
		return relAtoms;
	}


	/*
	 * Tries to filter out some atoms as irrelevant.
	 * If A is relevant than all lower atoms are added to the set.
	 * If A is irrelevant then all higher atoms are removed from the set.
	 * If neither then the atom is added and its children are checked recursively.
	 */
	private void filter(Atom atom, AtomicDecomposition ad, Signature sig, Set<Atom> relAtoms, Set<Atom> irrelAtoms, Set<Atom> possRelAtoms, Set<Atom> newAtoms) {

		MSSLabel label = getMSSLabel(atom);
		
		//GlobalTimer.start("filter");
		
		if (label.isAtomRelevant(sig)) {
			
			//GlobalTimer.stop("filter");
			RELEVANT_COUNT++;
			//GlobalTimer.start("dependencies");
			//The atom is provably relevant, grab the ideal			
			//TODO BFS on graphs is pretty slow at the moment
			for (Atom lower : ad.getDependencies(atom)) {
				
				if (relAtoms.add(lower)) newAtoms.add(lower);
			}
			
			//GlobalTimer.stop("dependencies");
			
		} else if (label.isComplete() /*|| SetUtils.containsSuperset(((SeedSigBasedLabel)label).getSeedSignatures(), sig, true)*/) { 
			
			IRRELEVANT_COUNT++;			
			//The atom is 100% irrelevant
			relAtoms.remove(atom);
			possRelAtoms.remove(atom);
			newAtoms.remove(atom);
			irrelAtoms.add(atom);

		} else {
			//We are not sure. The atom is *possibly* relevant.
			if (possRelAtoms.add(atom)) newAtoms.add(atom);
		}
		
		//GlobalTimer.stop("filter");
	}

	
	private Set<OWLAxiom> extractFromRelevantAtoms(Set<Atom> relAtoms, Signature sig, AtomicDecomposition ad, boolean annotations) {
		//GlobalTimer.start("union_atoms");
		//Get all axioms - some upper approximation of the module
		Set<OWLAxiom> allAxioms = new HashSet<OWLAxiom>(relAtoms.size() * 5);
		
		for (Atom atom : relAtoms) {
			allAxioms.addAll(annotations ? atom.getAllAxioms() : atom.getAxioms());
		}
		
		allAxioms.addAll(ad.getGlobalAxioms());
		//GlobalTimer.stop("union_atoms");
		//If needed, we can do extraction from this approximation
		return allAxioms;
	}


	@Override
	public void setDecomposition(AtomicDecomposition ad) {
		m_ad = ad;
	}
}